package com.liuc.practice.bank.transfer.factory;

import com.alibaba.druid.util.StringUtils;
import com.liuc.practice.bank.transfer.annotation.Autowired;
import com.liuc.practice.bank.transfer.annotation.Service;
import com.liuc.practice.bank.transfer.annotation.Transactional;
import org.reflections.Reflections;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @Author Liu Chang
 * @Date 2021/2/24 3:51 下午
 */
public class BeanFactory {

    private static Map<String, Object> map = new HashMap<>();

    static  {
        /*
        Properties properties = Configuration.parseIndependentBean();
        setMap(properties);

        try {
            map = Configuration.setDependentObject(map);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        */

        setServiceAnnotatedBeansMapByScanPackage();

        for(Map.Entry<String, Object> entrySet: map.entrySet()) {
            try {
                maintainingDependencies(entrySet);
                transactionAnnotated(entrySet);
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }


    }

    /**
     * 根据 xml 中配置的 id（即接口名称）获取对应对象（当实现类比较多时，需要使用的接口实现类）
     **/
    public static Object getBean(String id) {
        return map.get(id);
    }

    /**
     * @service() 注解的处理
     * @service() 修饰在类上，用于替代硬编码的实例化对象
     *
     * 1. 全包扫描，被 @service(value) 注解的类
     * 2. 实例化这些类，并塞入 map
     */
    private static void setServiceAnnotatedBeansMapByScanPackage() {
        //通过反射技术，扫描包并获取反射对象集合
        Reflections transfer = new Reflections("com.liuc.practice.bank.transfer");
        // 获取包含 service 注解的所有类
        Set<Class<?>> serviceAnnotatedClazzs = transfer.getTypesAnnotatedWith(Service.class);

        for (Class<?> clazz : serviceAnnotatedClazzs) {
            try {
                // 获取实例化对象
                Object instance = clazz.getDeclaredConstructor().newInstance();
                // 返回被 @service(value) 修饰的中该注释的对象
                Service annotation = clazz.getAnnotation(Service.class);

                // 判断 @service(value) 中 value 是否为空
                if (StringUtils.isEmpty(annotation.value())) {

                    String clazzSimpleName = clazz.getSimpleName();
                    map.put(clazzSimpleName, instance);
                } else {
                    map.put(annotation.value(), instance);
                }
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @Autowired() 注解的处理
     * @Autowired() 修饰在局部变量上，用于维护依赖关系
     *
     * 1. 从 map 中获取每个 value 值，即 bean 对象
     * 2. 通过反射获取该类中的所有成员变量
     * 3. 遍历成员变量，看是否被 @Autowired 修饰
     * 4. 有的话则完成注入(有此依赖关系的，使用 setter )
     */
    private static void maintainingDependencies(Map.Entry<String, Object> entrySet) throws InvocationTargetException, IllegalAccessException {
        Object obj = entrySet.getValue();
        Class<?> clazz = obj.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();

        for (Field declaredField : declaredFields) {
            // 查看属性上面是否被 @Autowired 修饰
            if (declaredField.isAnnotationPresent(Autowired.class)) {
                String name = declaredField.getType().getSimpleName();
                String setMethodName = "set" + name;

                // set 方法为 public 方法，用 getMethods（） 即可
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (setMethodName.equalsIgnoreCase(method.getName())) {
                        method.invoke(obj, map.get(name));
                    }
                }
            }
        }
    }

    /**
     * @transactional() 注解的处理
     * @transactional() 修饰在类、方法上，用于维护事务
     *
     * 1. 从 map 中获取类上是否存在 @transactional() 注解
     * 2. 如果有，则使用代理对象
     * 3. 判断对象是否实现接口，如果有实现，则使用 JDK 动态代理，否则使用 CGLib 动态代理
     */
    private static void transactionAnnotated(Map.Entry<String, Object> entrySet) {
        Object obj = entrySet.getValue();
        Class<?> clazz = obj.getClass();
        if (clazz.isAnnotationPresent(Transactional.class)) {
            ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
            Class[] interfaces = clazz.getInterfaces();
            if (interfaces != null && interfaces.length > 0) {
                obj = proxyFactory.getJdkProxyObject(obj);
            } else {
                obj = proxyFactory.getCGLibProxyObject(obj);
            }
            map.put(entrySet.getKey(), obj);
        }
    }

    /**
     * 缓存 xml 中配置的全部 beans
     **/
    private static void setMap(Properties properties) {

        Enumeration<?> keyEnumeration = properties.propertyNames();
        while (keyEnumeration.hasMoreElements()) {
            String id = (String) keyEnumeration.nextElement();
            String className = (String) properties.get(id);

            map.put(id, initBean(className));
        }
    }

    /**
     *  通过反射初始化 xml 中定义的 bean
     **/
    private static Object initBean(String className) {
        try {
            // 使用 className.getClass() 初始化后的对象是 String  类型
//            Class<?> clazz = className.getClass();
            Class<?> clazz = Class.forName(className);
            try {
                Object object = clazz.getDeclaredConstructor().newInstance();
                return object;
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return null;
    }

}
